#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Samsung 4030 耗粉量监控脚本
用于监控打印过程中的4色TonerCartridge的实际打印页数、剩余容量、额定页产量、序列号、错误提示等信息
"""

import requests
import json
import csv
import time
import os
import re
import ast
import html
from datetime import datetime
from urllib3 import disable_warnings
from urllib3.exceptions import InsecureRequestWarning

# 禁用SSL警告
disable_warnings(InsecureRequestWarning)

# 配置参数
PRINTER_IP = "169.254.96.244"  # 打印机IP地址，可根据实际情况修改
REQUEST_INTERVAL = 0.5  # 请求间隔（秒）
CSV_FILENAME = "samsung4030_supplies_log.csv"  # CSV文件名


def get_headers(ip):
    """
    获取请求头
    
    Args:
        ip: 打印机IP地址
        
    Returns:
        dict: 请求头字典
    """
    return {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate, br, zstd",
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Connection": "keep-alive",
        "Cookie": "language=en; xuser=SWS2.0; login=true",
        "Host": ip,
        "Referer": f"https://{ip}/sws/index.html",
        "Sec-Fetch-Dest": "empty",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Site": "same-origin",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
        "sec-ch-ua": '"Google Chrome";v="141", "Not?A_Brand";v="8", "Chromium";v="141"',
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": '"Windows"'
    }

# 4色粉筒名称
TONER_COLORS = ['toner_black', 'toner_cyan', 'toner_magenta', 'toner_yellow']
# 需要监控的字段
MONITORED_FIELDS = ['cnt', 'remaining', 'capa', 'serial', 'newError']


def parse_javascript_object(text):
    """
    解析JavaScript对象字面量格式（非标准JSON）为Python字典
    
    Args:
        text: JavaScript对象字面量格式的字符串
        
    Returns:
        dict: 解析后的字典，如果失败返回None
    """
    try:
        # 清理文本：移除制表符，统一空格
        text = text.replace('\t', ' ')
        
        # 处理HTML实体（如 &#x2F; 转换为 /）
        # 先保护字符串内的内容
        strings = []
        string_pattern = r'"[^"]*"'
        
        def replace_string(m):
            idx = len(strings)
            strings.append(m.group(0))
            return f'__STRING_{idx}__'
        
        # 临时替换字符串
        text_no_strings = re.sub(string_pattern, replace_string, text)
        
        # 处理HTML实体（在字符串外）
        text_no_strings = html.unescape(text_no_strings)
        
        # 给键名加引号（匹配 word: 模式）
        # 需要更精确的匹配，避免匹配已引用的键和值
        # 匹配模式：单词字符后跟冒号，但前面不能是引号或数字
        text_no_strings = re.sub(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*:', r'"\1":', text_no_strings)
        
        # 恢复字符串
        for idx, string in enumerate(strings):
            text_no_strings = text_no_strings.replace(f'__STRING_{idx}__', string)
        
        # 使用ast.literal_eval解析（Python字典字面量格式）
        return ast.literal_eval(text_no_strings)
    except (ValueError, SyntaxError) as e:
        print(f"解析JavaScript对象失败: {e}")
        return None


def get_supplies_data(ip):
    """
    获取打印机耗材信息
    
    Args:
        ip: 打印机IP地址
        
    Returns:
        dict: JSON响应数据，如果失败返回None
    """
    url = f"https://{ip}/sws/app/information/supplies/supplies.json"
    
    try:
        headers = get_headers(ip)
        response = requests.get(url, headers=headers, verify=False, timeout=5)
        response.raise_for_status()
        
        # 获取响应文本（可能是JavaScript对象字面量格式，不是标准JSON）
        text = response.text
        
        # 解析响应（支持标准JSON和JavaScript对象字面量格式）
        data = parse_javascript_object(text)
        return data
    except requests.exceptions.Timeout:
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 请求超时")
        return None
    except requests.exceptions.RequestException as e:
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 请求失败: {e}")
        return None
    except Exception as e:
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 获取数据时出错: {e}")
        return None


def extract_toner_data(data):
    """
    从响应数据中提取4色粉筒的监控字段
    
    Args:
        data: JSON响应数据
        
    Returns:
        dict: 包含4色粉筒数据的字典，格式为 {color: {field: value, ...}, ...}
    """
    toner_data = {}
    
    for color in TONER_COLORS:
        if color in data:
            toner_info = data[color]
            toner_data[color] = {
                'cnt': toner_info.get('cnt', 0),
                'remaining': toner_info.get('remaining', 0),
                'capa': toner_info.get('capa', 0),
                'serial': toner_info.get('serial', ''),
                'newError': toner_info.get('newError', '')
            }
        else:
            # 如果某个颜色不存在，使用默认值
            toner_data[color] = {
                'cnt': 0,
                'remaining': 0,
                'capa': 0,
                'serial': '',
                'newError': ''
            }
    
    return toner_data


def has_changed(old_data, new_data):
    """
    检查数据是否有变化
    
    Args:
        old_data: 上一次的数据
        new_data: 当前的数据
        
    Returns:
        bool: 如果有任何变化返回True，否则返回False
    """
    if old_data is None:
        return True  # 第一次记录，视为有变化
    
    # 检查每个颜色的每个字段
    for color in TONER_COLORS:
        if color not in old_data or color not in new_data:
            return True
        
        for field in MONITORED_FIELDS:
            old_value = old_data[color].get(field, None)
            new_value = new_data[color].get(field, None)
            
            if old_value != new_value:
                return True
    
    return False


def write_csv_row(csv_writer, csv_file, timestamp, toner_data):
    """
    写入CSV文件一行数据
    
    Args:
        csv_writer: CSV writer对象
        csv_file: CSV文件对象（用于刷新）
        timestamp: 时间戳字符串
        toner_data: 粉筒数据字典
    """
    row = [timestamp]
    
    # 按颜色顺序添加数据：black, cyan, magenta, yellow
    for color in TONER_COLORS:
        if color in toner_data:
            for field in MONITORED_FIELDS:
                row.append(toner_data[color].get(field, ''))
        else:
            # 如果数据不存在，添加空值
            row.extend([''] * len(MONITORED_FIELDS))
    
    csv_writer.writerow(row)
    csv_file.flush()  # 立即刷新到文件，确保数据写入


def initialize_csv(csv_path):
    """
    初始化CSV文件，写入表头
    
    Args:
        csv_path: CSV文件路径
        
    Returns:
        file: CSV文件对象
    """
    file_exists = os.path.exists(csv_path)
    
    csv_file = open(csv_path, 'a', newline='', encoding='utf-8-sig')
    writer = csv.writer(csv_file)
    
    # 如果文件不存在，写入表头
    if not file_exists:
        header = ['时间']
        # 为每个颜色添加字段列名
        color_names = {'toner_black': '黑色', 'toner_cyan': '青色', 
                      'toner_magenta': '品红', 'toner_yellow': '黄色'}
        for color in TONER_COLORS:
            color_name = color_names.get(color, color)
            for field in MONITORED_FIELDS:
                field_names = {
                    'cnt': '实际打印页数',
                    'remaining': '剩余容量百分比',
                    'capa': '额定页产量',
                    'serial': '序列号',
                    'newError': '错误提示'
                }
                header.append(f"{color_name}_{field_names.get(field, field)}")
        
        writer.writerow(header)
    
    return writer, csv_file


def main():
    """主函数"""
    print("=" * 60)
    print("Samsung 4030 耗粉量监控脚本")
    print("=" * 60)
    print(f"打印机IP: {PRINTER_IP}")
    print(f"监控间隔: {REQUEST_INTERVAL}秒")
    print(f"CSV文件: {CSV_FILENAME}")
    print("=" * 60)
    print("按 Ctrl+C 停止监控")
    print("=" * 60)
    
    # 获取脚本所在目录
    script_dir = os.path.dirname(os.path.abspath(__file__))
    csv_path = os.path.join(script_dir, CSV_FILENAME)
    
    # 初始化CSV文件
    csv_writer, csv_file = initialize_csv(csv_path)
    
    # 保存上一次的数据用于比较
    last_toner_data = None
    
    # 记录总数和变化次数
    total_requests = 0
    total_changes = 0
    
    try:
        while True:
            # 获取当前时间
            timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            
            # 获取耗材数据
            data = get_supplies_data(PRINTER_IP)
            total_requests += 1
            
            if data is not None:
                # 提取4色粉筒数据
                toner_data = extract_toner_data(data)
                
                # 检查是否有变化
                if has_changed(last_toner_data, toner_data):
                    # 写入CSV
                    write_csv_row(csv_writer, csv_file, timestamp, toner_data)
                    total_changes += 1
                    
                    # 打印变化信息
                    print(f"[{timestamp}] 数据已更新 (总请求: {total_requests}, 记录: {total_changes})")
                    
                    # 更新上一次的数据
                    last_toner_data = toner_data.copy()
                else:
                    # 数据无变化，跳过记录
                    if total_requests % 20 == 0:  # 每20次请求打印一次状态
                        print(f"[{timestamp}] 监控中... (总请求: {total_requests}, 记录: {total_changes})")
            else:
                # 获取数据失败，继续尝试
                pass
            
            # 等待指定时间
            time.sleep(REQUEST_INTERVAL)
            
    except KeyboardInterrupt:
        print("\n" + "=" * 60)
        print("监控已停止")
        print(f"总请求次数: {total_requests}")
        print(f"总记录次数: {total_changes}")
        print(f"CSV文件已保存: {csv_path}")
        print("=" * 60)
    except Exception as e:
        print(f"\n发生错误: {e}")
        import traceback
        traceback.print_exc()
    finally:
        # 关闭CSV文件
        csv_file.close()


if __name__ == "__main__":
    main()

